// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/text_ranges.h"

#include "base/logging.h"

namespace media {

TextRanges::TextRanges()
{
    Reset();
}

TextRanges::~TextRanges()
{
}

void TextRanges::Reset()
{
    curr_range_itr_ = range_map_.end();
}

bool TextRanges::AddCue(base::TimeDelta start_time)
{
    typedef RangeMap::iterator Itr;

    if (curr_range_itr_ == range_map_.end()) {
        // There is no active time range, so this is the first AddCue()
        // attempt that follows a Reset().

        if (range_map_.empty()) {
            NewRange(start_time);
            return true;
        }

        if (start_time < range_map_.begin()->first) {
            NewRange(start_time);
            return true;
        }

        const Itr itr = --Itr(range_map_.upper_bound(start_time));
        DCHECK(start_time >= itr->first);

        Range& range = itr->second;

        if (start_time > range.last_time()) {
            NewRange(start_time);
            return true;
        }

        range.ResetCount(start_time);
        curr_range_itr_ = itr;
        return false;
    }

    DCHECK(start_time >= curr_range_itr_->first);

    Range& curr_range = curr_range_itr_->second;

    if (start_time <= curr_range.last_time())
        return curr_range.AddCue(start_time);

    const Itr next_range_itr = ++Itr(curr_range_itr_);

    if (next_range_itr != range_map_.end()) {
        DCHECK(next_range_itr->first > curr_range.last_time());
        DCHECK(start_time <= next_range_itr->first);

        if (start_time == next_range_itr->first) {
            // We have walked off the current range, and onto the next one.
            // There is now no ambiguity about where the current time range
            // ends, and so we coalesce the current and next ranges.

            Merge(curr_range, next_range_itr);
            return false;
        }
    }

    // Either |curr_range| is the last range in the map, or there is a
    // next range beyond |curr_range|, but its start time is ahead of
    // this cue's start time.  In either case, this cue becomes the new
    // last_time for |curr_range|.  Eventually we will see a cue whose
    // time matches the start time of the next range, in which case we
    // coalesce the current and next ranges.

    curr_range.SetLastTime(start_time);
    return true;
}

size_t TextRanges::RangeCountForTesting() const
{
    return range_map_.size();
}

void TextRanges::NewRange(base::TimeDelta start_time)
{
    Range range;
    range.SetLastTime(start_time);

    std::pair<RangeMap::iterator, bool> result = range_map_.insert(std::make_pair(start_time, range));
    DCHECK(result.second);

    curr_range_itr_ = result.first;
}

void TextRanges::Merge(
    Range& curr_range,
    const RangeMap::iterator& next_range_itr)
{
    curr_range = next_range_itr->second;
    curr_range.ResetCount(next_range_itr->first);
    range_map_.erase(next_range_itr);
}

void TextRanges::Range::ResetCount(base::TimeDelta start_time)
{
    count_ = (start_time < last_time_) ? 0 : 1;
}

void TextRanges::Range::SetLastTime(base::TimeDelta last_time)
{
    last_time_ = last_time;
    count_ = 1;
    max_count_ = 1;
}

bool TextRanges::Range::AddCue(base::TimeDelta start_time)
{
    if (start_time < last_time_) {
        DCHECK_EQ(count_, 0);
        return false;
    }

    DCHECK(start_time == last_time_);

    ++count_;
    if (count_ <= max_count_)
        return false;

    ++max_count_;
    return true;
}

base::TimeDelta TextRanges::Range::last_time() const
{
    return last_time_;
}

} // namespace media
